<?php
/**
 * Geneone: Content Management Framework
 * User Class
 *
 * http://geneone.neondragon.net/
 *
 * @package Geneone
 * @author Khlo
 * @version 1.0
 * @copyright 2004-2006 Geneone Development Team
 */
 
class Gene_User {
	/**
	 * @var int User ID
	 */
	var $userid;
	
	/**
	 * @var array User Info
	 */
	var $userinfo = null;
	
	/**
	 * @var array Profile Info
	 */
	var $profileinfo = null;
	
	/**
	 * @var array Properties
	 */
	var $properties = null;
	
	/**
	 * @var array Public Names
	 */
	var $publicNames = null;
	
	/**
	 * @var array User Groups
	 */
	var $groups = array();
	
	/**
	 * @var array User Groups Info
	 */
	var $groupinfo = array();
	
	/**
	 * @var string E-Mail Address
	 */
	var $email;
	
	/**
	 * @var array Permission Masks
	 */
	var $permissionmasks = array();
	
	/**
	 * @var array Permission cache
	 */
	var $permissions = null;
	
	/**
	 * Class Constructor.
	 *
	 * @var int $userid User ID
	 */
	function Gene_User($userid) {
		if ($userid) {
			$this->userid = $userid;
			
			$groups = $this->getGroups();
			foreach ($groups as $i) {
				$this->permissionmasks[] = "g".$i;
			}
			
			$this->permissionmasks[] = "member";
			$this->permissionmasks[] = "u".$userid;
			$this->permissionmasks[] = "*";
		} else {
			$this->userid = 0;
			
			$this->permissionmasks[] = "guest";
			$this->permissionmasks[] = "*";
		}
	}
	
	/**
	 * Create Gene_User object from User Info.
	 *
	 * @static
	 * @var array $info User Info
	 */
	function &fromInfo($info) {
		$u =& new Gene_User($info['user_id']);
		$u->userinfo = $info;
		return $u;
	}
	
	/**
	 * Create Gene_User object from ID.
	 *
	 * @static
	 * @var int $userid User ID
	 */
	function &fromUserID($userid) {
		$db =& Gene::getDatabase();
		
		$q = $db->prepare("SELECT * FROM ".Gene::tableName("user_info")." WHERE user_id=?");
		$res = $q->execute(array($userid));
			
		if ($res->numRows()) {
			$info = $res->fetchRow();
			$u =& Gene_User::fromInfo($info);
			return $u;
		} else {
			$u = false;
			return $u;
		}
	}
	
	/**
	 * Create Gene_User object from Public Name or E-Mail.
	 *
	 * @static
	 * @var string $name Public Name or E-Mail
	 */
	function &fromPublicName($name) {
		$db =& Gene::getDatabase();
		
		/* Does it look like an email? */
		if (strpos($name, '@')) {
			$q = $db->prepare("SELECT i.* FROM ".Gene::tableName("users")." u LEFT JOIN ".Gene::tableName("user_info")." i ON (i.user_id=u.user_id) WHERE email=?");
			$res = $q->execute(array($name));
		} else {
			$name = Gene_User::canonizePublicName($name);
			
			$q = $db->prepare("SELECT u.* FROM ".Gene::tableName("user_name_history")." n LEFT JOIN ".Gene::tableName("user_info")." u ON (n.user_id=u.user_id) WHERE n.name=?");
			
			$res = $q->execute(array($name));
		}
		
		if ($info = $res->fetchRow()) {
			$u =& Gene_User::fromInfo($info);
			return $u;
		} else {
			$u = false;
			return $u;
		}
	}
	
	/**
	 * Create a new user, and return Gene_User Object
	 *
	 * @static
	 *
	 * @var string $email E-Mail Address
	 * @var string $password Password
	 * @var string $name Public Name
	 * @var array $groups Array of Member Groups
	 *
	 * @return mixed Gene_User on success; PEAR_Error on error.
	 */
	function &newUser($email, $password, $name, $groups=array()) {
		$db =& Gene::getDatabase();
		
		require_once 'Validate.php';
		
		/* Check email is valid and not being used */
		if (!$email || !Validate::email($email)) {
			return PEAR::raiseError("You must provide a valid e-mail address.");
		}
		
		$email = strtolower($email); // lowercase email to check
		
		// Do we already have a user with this email?
		$q = $db->prepare ("SELECT * FROM ".Gene::tableName("users")." WHERE email=?");
		$res = $q->execute(array($email));
		if ($res->numRows()) {
			$r =& PEAR::raiseError("The e-mail address provided is already being used. Please use the forgotten password feature link if you've forgotten your password.");
			return $r;
		}
		$res->free();
		
		/* Check password is provided */
		if (!$password) {
			return PEAR::raiseError("You must provide a password.");
		}
		
		/* Check public name is between 4 and 30 characters and not used */
		$name = Gene_User::canonizePublicName($name);
		if (strlen($name) < 4 || strlen($name) > 32) {
			$r =&  PEAR::raiseError("Your public name must be between 4 and 30 characters long.");
			return $r;
		}
		if (Gene_User::fromPublicName($name)) {
			$r =&  PEAR::raiseError("The specified name is already used.");
			return $r;
		}
		
		// Get the highest number member
		$userId = $db->nextId(Gene::tableName("users"));

		$v = array(
			"user_id" => $userId,
			"email" => $email,
			"password" => md5($password),
		);
		
		// Insert into the DB
		$db->extended->autoExecute(
			Gene::tableName("users"),
			$v,
			MDB2_AUTOQUERY_INSERT
		);
		
		// No groups?
		if (!count($groups)) {
			$groups = Gene::getSetting("default_ugroups");
		}
		
		// Insert Group Information
		$g = $db->extended->autoPrepare (
			Gene::tableName("group_users"),
			array("group_id", "user_id"),
			MDB2_AUTOQUERY_INSERT
		);
		$ins = array();
		foreach ($groups as $i) {
			$ins[] = array($i, $userId);
		}

		$db->extended->executeMultiple($g, $ins);
		
		// Insert blank profile table entry
		$db->extended->autoExecute(
			Gene::tableName("user_info"),
			array(
				"user_id" => $userId
			),
			MDB2_AUTOQUERY_INSERT
		);
		
		$user =& new Gene_User($userId);
		$user->setPublicName($name);
		
		return $user;
	}
	
	/**
	 * Sets user's e-mail address
	 *
	 * @var $email string New E-Mail Address
	 * @return bool Status
	 */
	function setEmail($email) {
		require_once 'Validate.php';
		
		if (Validate::email($email)) {
			$db =& Gene::getDatabase();
			
			// Do we already have a user with this email?
			$q = $db->prepare("SELECT * FROM ".Gene::tableName("users")." WHERE email=?");
			$res = $q->execute(array($email));

			if ($res->numRows()) {
				return false;
			}
		
			$db->extended->autoExecute(
				Gene::tableName("users"),
				array("email" => $email),
				MDB2_AUTOQUERY_UPDATE,
				"user_id=".(int)$this->getUserId()
			);
			
			return true;
		} else {
			return false;
		}
	}
	
	/**
	 * Sets user's password
	 *
	 * @var $password string New Password
	 */
	function setPassword($password) {
		$db =& Gene::getDatabase();
		$db->extended->autoExecute(
			Gene::tableName("users"),
			array("password" => md5($password)),
			MDB2_AUTOQUERY_UPDATE,
			"user_id=".(int)$this->getUserId()
		);
		return true;
	}
	
	/**
	 * Sets the current user's public name
	 *
	 * @var $name string Public Name
	 *
	 * @return mixed TRUE on success; PEAR_Error upon error.
	 */
	function setPublicName($name) {
		$name = Gene_User::canonizePublicName($name);
		$db =& Gene::getDatabase();
		
		// Check Public Name is available
		if (strlen($name) < 4 || strlen($name) > 32) {
			return PEAR::raiseError("Your public name must be between 4 and 30 characters long.");
		}
		
		if ($pnuser =& Gene_User::fromPublicName($name)) {
			if ($pnuser->getUserId() != $this->getUserId()) {
				// This name used by someone else?
				return PEAR::raiseError("The specified name is already used by another user.");
			} else {
				// We're going back to an old name
				$db->extended->autoExecute(
					Gene::tableName("user_name_history"),
					array(
						"timestamp" => time()
					),
					MDB2_AUTOQUERY_UPDATE,
					"name=".$db->quote($name)
				);
			}
		} else {
			// New name selected... can we have a new one?
			$numnames = $this->getNumberPublicNames();
			if ($numnames['allowed'] > 0) {
				$db->extended->autoExecute(
					Gene::tableName("user_name_history"),
					array(
						"user_id" => $this->getUserId(),
						"name" => $name,
						"timestamp" => time()
					),
					MDB2_AUTOQUERY_INSERT
				);
			} else {
				return PEAR::raiseError("You have run out of name changes.");
			}
		}
		
		// If we've got here then we should change the user_info entry
		$db->extended->autoExecute(
			Gene::tableName("user_info"),
			array("name" => $name),
			MDB2_AUTOQUERY_UPDATE,
			"user_id=".(int)$this->getUserId()
		);
		
		$this->publicNames = null; // Get rid of name cache
		return true;
	}
	
	/**
	 * Sets profile information for the current user.
	 *
	 * @var array $profile Profile Information
	 */
	function setProfile($profile) {
		require_once 'Validate.php';
		
		foreach ($profile as $j => $k) {
			if (in_array($j, array("bio","gender","location","homepage","jabber"))) {
				// Remove badly formatted stuff
				if ($j == "jabber" AND !Validate::email($k) AND $k != "") {
					unset ($profile['jabber']);
				} else if ($j == "homepage" AND !Validate::uri($k, array("allowed_schemes" => array("http", "https"), "strict" => "")) AND $k != "") {
					unset ($profile['homepage']);
				} else {
					$this->userinfo[$j] = $k;
				}
			} else {
				unset ($profile[$j]);
			}
		}
		
		$db =& Gene::getDatabase();
		
		$db->extended->autoExecute(
			Gene::tableName("user_info"),
			$profile,
			MDB2_AUTOQUERY_UPDATE,
			"user_id=".(int)$this->getUserId()
		);
	}
	
	/**
	 * Returns user ID of user object
	 *
	 * @return integer User ID (0 indicates guest)
	 */
	function getUserId() {
		return $this->userid;
	}
	 
	/**
	 * Gets an array of Groups of the current user.
	 *
	 * @return array Groups of User
	 */
	function getGroups() {
		$db =& Gene::getDatabase();
		
		if (!count($this->groups)) {
			$q = $db->prepare("SELECT group_id FROM ".Gene::tableName("group_users")." WHERE user_id=?");
			$res = $q->execute(array($this->getUserId()));
			
			$this->groups = $res->fetchCol();

			$res->free();
		}
		
		return $this->groups;
	}
	
	/**
	 * Gets an array of Groups and Group Info of the current user.
	 *
	 * @return array Group Info of User
	 */
	function getGroupInfo() {
		$db =& Gene::getDatabase();
		
		if (!count($this->groupinfo)) {
			$q = $db->prepare ("SELECT u.group_id, g.* FROM ".Gene::tableName("group_users")." u LEFT JOIN ".Gene::tableName("groups")." g ON (u.group_id=g.gid) WHERE u.user_id=?");
			$res = $q->execute(array($this->getUserId()));
			
			$this->groupinfo = $res->fetchAll();
			
			$res->free();
		}
		
		return $this->groupinfo;
	}
	
	/**
	 * Gets information on the user.
	 *
	 * @return array Information of User
	 */
	function getInfo() {
		$db =& Gene::getDatabase();
		
		if ($this->userinfo === null) {
			$q = $db->prepare("SELECT * FROM ".Gene::tableName("user_info")." WHERE user_id=?");
			$res = $q->execute(array($this->getUserId()));
			
			$this->userinfo = $res->fetchRow();

			$res->free();
		}
		
		return $this->userinfo;
	}
	
	/**
	 * Lists all user properties.
	 *
	 * @return array User Properties
	 */
	function &listProperties() {
		$db =& Gene::getDatabase();
		
		if ($this->properties === null) {
			$q = $db->prepare ("SELECT property_id, value FROM ".Gene::tableName("user_properties")." WHERE user_id=?");
			$res = $q->execute(array($this->getUserId()));
			
			$properties = $res->fetchAll();
			$this->properties = array();
			foreach ($properties as $i) {
				$this->properties[$i['property_id']] = $i['value'];
			}
			
			$res->free();
		}
		
		return $this->properties;
	}
	
	/**
	 * Gets a specific property of the user.
	 *
	 * @var string $name Property Name
	 *
	 * @return string User Property
	 */
	function getProperty($name) {
		$p =& $this->listProperties();
		
		if (isset($p[$name])) {
			return $p[$name];
		} 
		
		return false;
	}
	
	/**
	 * Sets a property of the user.
	 *
	 * @var string $name Property Name
	 * @var string $value Property Value
	 */
	function setProperty($name, $value='') {
		$p =& $this->listProperties();
		$db =& Gene::getDatabase();
		
		if (isset($p[$name])) {
			if ($value == '') {
				$q = $db->prepare ("DELETE FROM ".Gene::tableName("user_properties")." WHERE user_id=? AND property_id=?");
				$res = $q->execute(array($this->getUserId(), $name));
				
				unset($p[$name]);
			} else {
				$db->extended->autoExecute(
					Gene::tableName("user_properties"),
					array(
						"value" => $value,
					),
					MDB2_AUTOQUERY_UPDATE,
					 "user_id='".$this->getUserId()."' AND property_id=".$db->quote($name)
				);
				
				$p[$name] = $value;
			}
		} else {
			$db->extended->autoExecute(
				Gene::tableName("user_properties"), 
				array(
					"user_id" => $this->getUserId(),
					"property_id" => $name,
					"value" => $value,
				),
				MDB2_AUTOQUERY_INSERT
			);
			
			$p[$name] = $value;
		}
	}
	
	/**
	 * Gets all of current user's previous and current public names
	 *
	 * @return array Previous Names and Date Info
	 */
	function getAllPublicNames() {
		$db =& Gene::getDatabase();
		
		if ($this->publicNames === null) {
			$q = $db->prepare ("SELECT name, timestamp FROM ".Gene::tableName("user_name_history")." WHERE user_id=? ORDER BY timestamp DESC");
			$res = $q->execute(array($this->getUserId()));
			$this->publicNames = $res->fetchAll();
		}
		
		return $this->publicNames;
	}
	
	/**
	 * Get number of public names used, allowed and left.
	 *
	 * @return array Public Name Count
	 */
	function getNumberPublicNames() {
		$names = $this->getAllPublicNames();
		$total = Gene::getSetting("max_public_names");
		
		return array(
			"used" => count($names),
			"allowed" => $total - count($names),
			"total" => $total,
		);
	}
	
	/**
	 * Gets user's permission masks
	 *
	 * @return bool Permission masks
	 */
	 function getPermissionMasks() {
		 return $this->permissionmasks;
	 }
	 
	/**
	 * Loads permissions.
	 */
	function loadPermissions() {
		// Never, ever create new masks with the value of 'i' or 'o'. They are reserved
		// for inherit and owner.
		
		$db =& Gene::getDatabase();
		$masks = $this->getPermissionMasks();
		foreach ($masks as $j => $k) {
			$masks[$j] = "'".$k."'";
		}
		$validpermissions = $db->queryCol("SELECT permission_id FROM ".Gene::tableName("permissions")." WHERE mask IN (".implode(",", $masks).")");
		
		// Remove duplicates
		$this->permissions = array_unique($validpermissions);
	}
	
	/**
	 * Returns users permissions
	 *
	 * @return array Permissions
	 */
	function getPermissions() {
		if ($this->permissions === null) {
			$this->loadPermissions();
		}
		
		return $this->permissions;
	}
	
	/**
	 * Returns whether user has permission to do something.
	 *
	 * @var string $mask Permission
	 * @return bool Whether user has permission
	 */
	function hasPermission($mask) {
		if ($this->permissions === null) {
			$this->loadPermissions();
		}
		
		return in_array($mask, $this->permissions);
	}
	
	/**
	 * Returns user's e-mail address
	 *
	 * @return string E-Mail Address
	 */
	function getEmail() {
		$db =& Gene::getDatabase();
		
		if ($this->email === null) {
			$q = $db->prepare("SELECT email FROM ".Gene::tableName("users")." WHERE user_id=?");
			$res = $q->execute(array($this->getUserId()));
			$this->email = $res->fetchOne();
			$res->free();
		}
		
		return $this->email;
	}
	
	/**
	 * Checks specified password for match with user's password.
	 *
	 * Can be used to check password for higher security activities.
	 *
	 * @var $password string Password
	 * @return bool Whether password is correct.
	 */
	function checkPassword($password) {
		$db =& Gene::getDatabase();
		$q = $db->prepare ("SELECT password FROM ".Gene::tableName("users")." WHERE user_id=?");
		$res = $q->execute(array($this->getUserId()));
		$userpassword = $res->fetchOne();
		$res->free();
		
		if ($userpassword == md5($password)) {
			return true;
		} else {
			return false;
		}
	}
	
	/**
	 * Canonizes a public name.
	 *
	 * @var $name string Name
	 * @return string Canonized Name
	 */
	function canonizePublicName($name) {
		// Underscores and Dots probably mean Spaces
		$name = str_replace(array(".", "_"), " ", $name);
		
		// Remove Silly Whitespace
		$name = trim(preg_replace('/\s\s+/', ' ', $name));
		
		// Remove all characters except alphanumeric, space and brackets
		$name = preg_replace("/[^[:alnum:] \(\)\-]/e", "", $name);
		
		// All lowercase except first letter of each word
		$name = ucwords(strtolower($name));
		$name = preg_replace('/[\(\-\)][a-z]/e', "strtoupper('$0')", $name);
		
		return $name;
	}
}
?>